package edu.uj.petri.view;

import edu.uj.petri.commands.Memento;
import edu.uj.petri.commands.RemoveArcCommand;
import edu.uj.petri.commands.Restorable;
import edu.uj.petri.document.Document;
import edu.uj.petri.model.Arc;
import edu.uj.petri.model.Transition;
import edu.uj.util.Observer;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

// TODO implement

public abstract class ArcView extends DiagramElement implements Observer, Restorable {
    
    private boolean deleted;

    protected NodeView begin;
    protected NodeView end;
    
    public Arc arc;
    
    protected List<LinkingPoint> linkingPoints = new ArrayList<LinkingPoint>();
    protected List<ArcSegment> segments = new ArrayList<ArcSegment>();

    /**
     * point where end of this arc is when end has not been set
     */
    protected Point2D floatingEnd;

    /**
     * throws exception is begin is
     */
    public ArcView(NodeView begin) {
        
        this.begin = begin;
    }

    public boolean containsPoint(Point2D point) {
       
        for (ArcSegment s: segments)
            if (s.containsPoint(point)) return true;
            
        return false;
    }

    public boolean crossesRectangle(Rectangle2D rect) {
        
        for (LinkingPoint p : linkingPoints) 
            if (p.crossesRectangle(rect)) return true;
        
        for (ArcSegment s: segments)
            if (s.crossesRectangle(rect)) return true;
            
        return false;
    }

    public void draw(Canvas canvas) {
        
    }

    public void setSelected(boolean selected) {
        
        this.selected = selected;
        
        for (LinkingPoint p : linkingPoints) 
            p.setSelected(selected);
        
        for (ArcSegment s: segments)
            s.setSelected(selected);
            
    }

    public boolean canBeMoved(Point2D startPoint) {
        
        for (int i = 1; i < linkingPoints.size() - 1; i++)
            if (linkingPoints.get(i).canBeMoved(startPoint))
               return true;
        
        return false;
    }

    public void move(Point2D start, Point2D end) {

        
        for (int i = 0; i < linkingPoints.size(); i++)
            if (linkingPoints.get(i).canBeMoved(start))
            {
                linkingPoints.get(i).move(start, end);
                if (i == 1) begin.arcsChanged();
                if (i == linkingPoints.size() - 2) this.end.arcsChanged();
            }
    }

    public void doubleClick( Point2D point ) {
        
        addLinkingPoint( point );
    }

    public ContextMenu getContextMenu() {
        
        ContextMenu retval = new ContextMenu();
        retval.addItem(ResourceBundle.getBundle("edu/uj/petri/messages").getString("Remove"), new RemoveArcCommand(this));
        return retval;
    }

    /**
     * moves end to a specified position when the arc has begin but is still not 
     *    connected with end
     */
    public void moveEnd(Point2D position) {
        
        floatingEnd = position;
    }

    public void removeLinkingPoint(LinkingPoint point) {
        
        ArcSegment begin = null, end = null;
        
        for (ArcSegment s : segments)
            if (s.getBegin() == point) end = s;
            else if (s.getEnd() == point) begin = s;
        
        if (end.getNodeEnd() == null) begin.setEnd( end.getEnd() );
        else begin.setNodeEnd( end.getNodeEnd() );
        
        segments.remove( end );
        linkingPoints.remove(point);        
    }
    
    /**
     * splits a segment by adding a linking point in place of mouse click
     */
    public void addLinkingPoint(Point2D point) {
        
        for (ArcSegment s: segments) 
            if (s.containsPoint(point))
            {
                splitSegment(s, point);
                return;
            }
    }
    
    public void addLinkingPoint(LinkingPoint p, ArcSegment segment) {
        
        ArcSegment arc;
        if (segment.getEnd() != null) arc = new ArcSegment( p, segment.getEnd());
        else arc = new ArcSegment( p, segment.getNodeEnd());
        segment.setEnd( p );
        p.setSelected(true);
        linkingPoints.add( p );
        segments.add( arc );
    }

    /**
     * splits given segment into two by adding a linking point
     */
    private void splitSegment(ArcSegment segment, Point2D point) {

        LinkingPoint p = new LinkingPoint( point );
        
        ArcSegment arc;
        if (segment.getEnd() != null) arc = new ArcSegment( p, segment.getEnd());
        else arc = new ArcSegment( p, segment.getNodeEnd());
        segment.setEnd( p );
        p.setSelected(true);
        linkingPoints.add( p );
        segments.add( arc );
    }
    
    /**
     * finally sets end of the arc
     */
    public void setEnd(NodeView end) {
        if( !canBeEnd(end) )
            throw new IllegalArgumentException(ResourceBundle.getBundle("edu/uj/petri/messages").getString(
                    "cannot_be_end"));
        this.end = end;
        this.floatingEnd = null; // finished floating
        finishCreating();
        ArcSegment arc = new ArcSegment( begin, end );
        segments.add( arc );
        
        end.arcs.add(this);
        begin.arcs.add(this);
        
    }
    
    /** performs needed actions after setting end of arc. Template method. */
    protected abstract void finishCreating();
    
    public void removeFrom(View view) {
        
        if (removed) return;
        removed = true;
        
        if (arc.getFrom() instanceof Transition) {
            Transition transition = (Transition)arc.getFrom();
            transition.removeOutgoingArc(arc);
        } else {
            Transition transition = (Transition)arc.getTo();
            transition.removeIncomingArc(arc);
        }
        
        if (!begin.removed) begin.arcs.remove(this);
        if (!end.removed) end.arcs.remove(this);
        
        Document.getActiveDocument().getView().removeElement(this);
    }

    /**
     * returns false if such element cannot be end of arc (eg. method is called 
     *    with a place and the arc already begins in a place).
     */
    public abstract boolean canBeEnd(NodeView node);
    
    public NodeView getBegin() {
        return begin;
    }
    
    public NodeView getEnd() {
        return end;
    }

    public void applyMemento(Memento memento) {
        ArcViewMemento myMemento = (ArcViewMemento)memento;
        if (myMemento.arcMemento != null) {
            arc.applyMemento(myMemento.arcMemento);
        }
        begin.applyMemento(myMemento.beginMemento);
        if (myMemento.endMemento != null) {
            end.applyMemento(myMemento.endMemento);
        }
        for(LinkingPoint point : linkingPoints) {
            if (myMemento.linkingPointMementos.containsKey(point))
                point.applyMemento(myMemento.linkingPointMementos.get(point));
        }
        removed = myMemento.removed;
    }

    public Memento getMemento() {
        ArcViewMemento retval = new ArcViewMemento();
        if (arc != null) {
            retval.arcMemento = arc.getMemento();
        }
        retval.beginMemento = begin.getMemento();
        if (end != null) {
            retval.endMemento = end.getMemento();
        }
        retval.linkingPointMementos = new HashMap<LinkingPoint,Memento>();
        for(LinkingPoint point : linkingPoints) {
            retval.linkingPointMementos.put(point, point.getMemento());
        }
        retval.removed = removed;
        return retval;
    }

    public ContextMenu getMenu( Point point) {
        
        for (LinkingPoint p: linkingPoints) 
            if (p.containsPoint(point))
            {                
                return p.getContextMenu(this);
            }
        
         return getContextMenu();
    }
    
    static class ArcViewMemento extends Memento {
        private Memento arcMemento, beginMemento, endMemento;
        private Map<LinkingPoint,Memento> linkingPointMementos;
        private boolean removed;
    }
    

}
